/*
 * 
 * Copyright 2011 visual-analysis-of-cancer.
 * This file is part of visual-analysis-of-cancer.
 * 
 * visual-analysis-of-cancer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * visual-analysis-of-cancer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with kOSMik.  If not, see <http://www.gnu.org/licenses/>.
 *  
 * visual-analysis-of-cancer:
 * An tool to analye the WHO and GLOBOCAN cancer data.
 * http://code.google.com/p/visual-analysis-of-cancer/
 * 
 * 
 * 
 * Authors: Erik Sniegula, Johannes Dahlke
 * 
 * Module:  Programming in Processing for Visual Analytics
 * Group:   20
 * 
 * Created: Aug. 2011
 * Version: 1.0
 *
 */
 




package de.fuberlin.processing.who.cancer.gui.healthgraph;

import java.awt.Rectangle;
import java.util.Collection;
import java.util.Iterator;
import java.util.Observer;
import java.util.Vector;

import controlP5.ControlP5;
import controlP5.Controller;
import controlP5.ControllerDisplay;

import processing.core.PVector;


import de.fuberlin.processing.who.cancer.controlp5ex.CustomTooltip;
import de.fuberlin.processing.who.cancer.interaction.CausesBroadcaster;
import de.fuberlin.processing.who.cancer.utils.Notification;
import de.fuberlin.processing.who.cancer.utils.Test;


public class OrbitalSystemImpl extends Thread implements OrbitalSystem {

	private final Vector<Orbit> orbits = new Vector<Orbit>();

	private boolean updateRequested = false;

	private Rect dimensions;
	
	private CustomTooltip tooltipSystem;


	public OrbitalSystemImpl( int left, int top, int width, int height, CustomTooltip tooltipSystem) {
		super();
		dimensions = new Rect( left, top, width, height);
		this.tooltipSystem = tooltipSystem;
		setDaemon( true);
		start();
	}
	

	public synchronized void registerOrbit( Orbit orbit) {
		if ( !orbits.contains( orbit)) {
			orbits.add( orbit);
			tooltipSystem.register( orbit);
			rearrangeOrbits();
		}
	}


	public void registerOrbits( Collection<Orbit> theNewOrbits) {
		for ( Orbit newOrbit : theNewOrbits) {
			registerOrbit( newOrbit);
		}
	}
	
	
	public synchronized void unregisterOrbit( Orbit orbit) {
		if ( orbits.contains( orbit)) {
			// unregister controller in controlP5
			orbit.remove();
			tooltipSystem.unregister( (Controller) orbit);
			// remove orbit from orbital system
			orbits.remove( orbit);
			rearrangeOrbits();
		}
	}


	public synchronized void clear() {
		for ( Orbit orbit : orbits) {
			// unregister controller from controlP5
			orbit.remove();
		}
		// clear the orbit list
		orbits.clear();
		tooltipSystem.clearAllInstancesOf( CancerDisc.class);
		System.gc();
	}


	private void removeNonDisplayableOrbits() {
		Orbit currentOrbit;
		for ( int i = orbits.size(); i > 0; i--) {
			currentOrbit = orbits.get( i - 1);
			if ( Test.isUnassigned( currentOrbit.getPrimalRect())) {
				unregisterOrbit( currentOrbit);
			}
		}
	}


	private Rect detectByOrbitsOccupiedSpace() {
		Rect embracingRect = null;
		Iterator<Orbit> i = orbits.iterator();
		Orbit orbit;
		if ( i.hasNext()) {
			orbit = i.next();
			embracingRect = new Rect( orbit.getPrimalRect());
			orbit.setLeftmost( orbit);
			orbit.setTopmost( orbit);
			orbit.setRightmost( orbit);
			orbit.setBottommost( orbit);
			
			while ( i.hasNext()) {
				orbit = i.next();
				Rect r = orbit.getPrimalRect();
				if ( r.getLeft() < embracingRect.getLeft()) {
					float tmp = embracingRect.getRight();
					embracingRect.setLeft( r.getLeft());
					embracingRect.setRight( tmp);
					orbit.setLeftmost( orbit);
				}
				if ( r.getRight() > embracingRect.getRight()) {
					embracingRect.setRight( r.getRight());
					orbit.setRightmost( orbit);
				}	
				if ( r.getTop() < embracingRect.getTop()) {
					float tmp = embracingRect.getBottom();
					embracingRect.setTop( r.getTop());
					embracingRect.setBottom( tmp);
					orbit.setTopmost( orbit);
				}
				if ( r.getBottom() > embracingRect.getBottom()) {
					embracingRect.setBottom( r.getBottom());
					orbit.setBottommost( orbit);
				}
			}
		}
		return embracingRect;
	}


	private void rearrangeOrbits() {
		Rect occupiedSpace;
		removeNonDisplayableOrbits();
		if ( Test.isAssigned( occupiedSpace = detectByOrbitsOccupiedSpace())) {
			for ( Orbit orbit : orbits) {
				mapOrbit( orbit, occupiedSpace, this.dimensions);
			}
		}
		updateRequested = true;
	}


	private void mapOrbit( Orbit orbit, Rect fromRect, Rect toRect) {
		Rect oldOrbitRect = orbit.getPrimalRect();
		Rect newOrbitRect = new Rect();
		newOrbitRect.setLeft( ControlP5.papplet.map( oldOrbitRect.getLeft(),
				fromRect.getLeft(), fromRect.getRight(), toRect.getLeft(),
				toRect.getRight()));
		newOrbitRect.setRight( ControlP5.papplet.map( oldOrbitRect.getRight(),
				fromRect.getLeft(), fromRect.getRight(), toRect.getLeft(),
				toRect.getRight()));
		newOrbitRect.setTop( ControlP5.papplet.map( oldOrbitRect.getTop(),
				fromRect.getTop(), fromRect.getBottom(), toRect.getTop(),
				toRect.getBottom()));
		newOrbitRect.setBottom( ControlP5.papplet.map( oldOrbitRect.getBottom(),
				fromRect.getTop(), fromRect.getBottom(), toRect.getTop(),
				toRect.getBottom()));
		orbit.setRect( newOrbitRect);
	}


	private synchronized void broadcastUpdate() {
		updateRequested = false;
		for ( Orbit theUpdatingOrbit : orbits) {
			for ( Orbit anRelatedOrbit : orbits) {
				if ( theUpdatingOrbit != anRelatedOrbit)
					updateRequested |= theUpdatingOrbit
							.updateToAnRelatedOrbit( anRelatedOrbit);		  
			}
			tooltipSystem.updateTooltipController( theUpdatingOrbit);
		}
		if ( updateRequested) {
			rearrangeOrbits();
		} else {
			showAllOrbits();
	  }
	}

  private void showAllOrbits() {
  	for (  Orbit orbit : orbits) {
			orbit.show();
		}
  }
	
	
	@Override
	public synchronized void doUpdate() {
		updateRequested = true;
	}


	@Override
	public void run() {
		while ( !isInterrupted()) {
			if ( updateRequested)
				broadcastUpdate();
			else {
				try {
					sleep( 25);
				} catch ( InterruptedException e) {
					Notification.printDebugException( e);
					interrupt();
				}
			}
		}
	}

}
